package network;

import java.security.PublicKey;

public class NetworkingProtocol {

	/**
		Protocol Specifications

		Client				State					Server
							0-0
						(initial connection)
							1-0
			->	REQ [ENC] PRG:$prg VER:$ver [KEY:$key] ->
	(note: the protocol requires that the fields be in this order)
		IF jibberish
			<-					DNY					<-
		IF $prg is bad
			<-					DNY PRG				<-
		IF $ver is bad
			<-					DNY VER				<-
		IF not ENC & force ENC (server)
			<- 					DNY ENC				<-
			IF $key is bad
			<-					DNY KEY				<-
					(exit on all of the above)
		FI ALL

		IF $prg is good & $ver is good & not ENC
			<-			ACP PRG:$prg VER:$ver		<-
		IF $prg is good & $ver is good & ENC & $key is good
			<-	ACP ENC PRG:$prg VER:$ver KEY:$key	<-
					(switch over to encryption)
		FI
							1-2
					(communications as regular)
							2-2

							(to exit)
			1:
			<-				REQ EXIT				<-
							2-3
			->				ACP EXIT				->
							4-3
			2:			
			->				REQ EXIT				->
							3-2
			<-				ACP EXIT				<-
							3-4
							(exit)
							4-4
	 */


	public static final int WAITING = 0;//Initial state.
	public static final int CONNECTING = 1;//connecting
	public static final int CONNECTED = 2;//connected.
	public static final int GOODBYE = 3;//waiting for confirmation of disconnect.
	public static final int EXITED = 4;//connection closed

	public static final String REQ = "REQ";
	public static final String ACP = "ACP";
	public static final String DNY = "DNY";
	public static final char split = ' ';
	public static final String ENC = "ENC";
	public static final String KEY = "KEY:";
	public static final String PRG = "PRG:";
	public static final String VER = "VER:";
	public static final String ERR = "ERROR";


	private static final String version="2.0";//version of the protocol

	public static void main(String[] args){
		String[] a = protocolParser("REQ PRG:PHROX VER:2.0");
		for(String str : a){
			System.out.println(str);
		}
		a = protocolParser("REQ ENC PRG:PHROX VER:2.0 KEY:0010102896141");
		for(String str : a){
			System.out.println(str);
		}
	}

	private int state = WAITING;
	private boolean error = false;//set to true if theres an error on connection
	private boolean encrypt = false;
	private PublicKey key = null;
	public final String program;//the program that is running the protocol


	public NetworkingProtocol(String str, boolean b){
		program = str;
		encrypt = b;
	}
	/**
	 * 
	 * @return returns the state the current protocol is in:
	 * Waiting, connected or goodbye(1,2,3)
	 */
	public int getState(){
		return state;
	} 

	public boolean connection_protocol(String str){
		boolean ret = false;
		if( str == null || str.equals(ERR) ){
			error = true;
			return false;
		}
		switch( state ){
		case WAITING:
			if( str.startsWith(REQ) ) {
				String[] strs = protocolParser(str);
				if( strs[0].equals(program) && strs[1].equals(version) ){
					if( strs[2].equals("YES") ){
						PublicKey tmp = NetworkVars.keyRead.decodePublicKey(strs[3].getBytes());
						if( encrypt && NetworkVars.keyRing.inKeyRing( tmp ) ){
							//if we don't have encryption then this is no go.
							ret = true;
							state = CONNECTING;
							key = tmp;
							//accepted with encryption
						}
					}else{
						ret = true;
						state = CONNECTING;
					}
				}
			}
			break;
		case CONNECTING:
			if( str.startsWith(ACP) ){
				String[] strs = protocolParser(str);
				if( strs[0].equals(program) && strs[1].equals(version) ){
					if( strs[2].equals("YES") ){
						PublicKey tmp = NetworkVars.keyRead.decodePublicKey(strs[3].getBytes()); 
						if( encrypt && NetworkVars.keyRing.inKeyRing( tmp ) ){
							//if we don't have encryption then this is no go.
							ret = true;
							state = CONNECTED;
							key = tmp;
							//accepted with encryption
						}
					}else{
						ret = true;
						state = CONNECTED;
					}
				}
			}
			break;
		case CONNECTED:
		default:
			//error
		}
		error = !ret;
		return ret;
	}

	public String connection_protocol_send(){
		String toReturn = null;
		if(error){
			toReturn = "DNY";
		} else {
			switch( state ){
			case WAITING:
				if( encrypt ){
					toReturn = REQ +split+
						ENC +split+
						PRG+program +split+
						VER+version +split+ 
						KEY+ new String(NetworkVars.pubKey.getEncoded());
				} else {
					toReturn = REQ +split+ PRG+program +split+ VER+version;
				}
				state = CONNECTING;
				break;
			case CONNECTING:
				if( encrypt ){
					toReturn = ACP +split+
					ENC +split+
					PRG+program +split+
					VER+version +split+ 
					KEY+ new String(NetworkVars.pubKey.getEncoded());
				} else {
					toReturn = ACP +split+ PRG+program +split+ VER+version;
				}
				state = CONNECTED;
				break;
			case CONNECTED:
				toReturn = "";
				break;
			default:
				//error, handled by defaulte start of toReturn (null)
			}
		}
		return toReturn;
	}

	public void reset(){
		state = WAITING;
	}

	public PublicKey getKey(){
		if( state==CONNECTED && encrypt){
			return key;
		}
		return null;
	}
	
	/**
	 * 
	 * @param str Protocol String to parse
	 * @return returns an array formatted the following way:<br>
	 * [0] Program<br>
	 * [1] Version<br>
	 * [2] Encryption? (if applicable)<br>
	 * [3] KEY (if applicable)<br>
	 */
	private static String[] protocolParser(String str){
		String[] ret = { "ERROR" , "UNKNOWN" , "NO", "" };
		int prg=str.indexOf(PRG), ver=str.indexOf(VER);
		ret[0] = str.substring(prg+4,ver-1);
		if( str.contains(ENC) ){
			ret[2] = "YES";
			//look for key
			if( str.contains(KEY) ){
				int enc = str.indexOf(KEY);
				ret[3] = str.substring( enc+4 ); 
				ret[1] = str.substring(ver+4, enc-1);
			}
		} else {
			ret[1] = str.substring(ver+4);
		}
		return ret;
	}
}